#include "TcpSocket.h"
#include <memory>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include "InnerType.h"
#include "IpV4Address.h"
#include "IpV6Address.h"
#include "PlatLogger.h"
#include "SocketOpt.h"
#include "Util.h"
namespace xncc {
namespace foundation {
    TcpSocket::~TcpSocket() = default;

    bool TcpSocket::getTcpInfo(struct tcp_info* tcpi) const
    {
#if defined(__CYGWIN__)
        return false;
#else
        socklen_t len = sizeof(*tcpi);
        shared::memZero(tcpi);
        return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0;
#endif
    }

    std::string TcpSocket::getTcpInfoString() const
    {
#if defined(__CYGWIN__)
        return "";
#else
        struct tcp_info    tcpi = {};
        bool               ok   = getTcpInfo(&tcpi);
        std::ostringstream oss;

        if (ok) {
            oss << "unrecovered=" << static_cast<unsigned>(tcpi.tcpi_retransmits) << " rto=" << tcpi.tcpi_rto
                << " ato=" << tcpi.tcpi_ato << " snd_mss=" << tcpi.tcpi_snd_mss << " rcv_mss=" << tcpi.tcpi_rcv_mss
                << " lost=" << tcpi.tcpi_lost << " retrans=" << tcpi.tcpi_retrans << " rtt=" << tcpi.tcpi_rtt
                << " rttvar=" << tcpi.tcpi_rttvar << " sshthresh=" << tcpi.tcpi_snd_ssthresh
                << " cwnd=" << tcpi.tcpi_snd_cwnd << " total_retrans=" << tcpi.tcpi_total_retrans;
        }
        return oss.str();
#endif
    }

    void TcpSocket::bind()
    {
        int ret = -1;
        if (sockaddr_->Type() == shared::XNCC_ADDR_TYPE::IPV4_TCP) {
            const IpV4Address::s_ptr v4Addr = std::dynamic_pointer_cast<IpV4Address>(sockaddr_);
            ret = ::bind(sockfd_, v4Addr->getSockAddr(), static_cast<socklen_t>(sizeof(struct sockaddr_in)));
        }
        else if (sockaddr_->Type() == shared::XNCC_ADDR_TYPE::IPV6_TCP) {
            const IpV6Address::s_ptr v6Addr = std::dynamic_pointer_cast<IpV6Address>(sockaddr_);
            ret = ::bind(sockfd_, v6Addr->getSockAddr(), static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
        }
        if (ret < 0) {
            throw shared::system_exception("bind socket failed");
        }
    }

    void TcpSocket::listen()
    {
        int ret = ::listen(sockfd_, SOMAXCONN);
        if (ret < 0) {
            throw shared::system_exception("listen socket failed");
        }
    }

    int TcpSocket::accept(std::shared_ptr<XnccAddress> peeraddr)
    {
        struct sockaddr_in6 v6addr = {};
        shared::memZero(v6addr);
        auto             addrlen = static_cast<socklen_t>(sizeof v6addr);
        struct sockaddr* addr    = SocketOpt::sockaddr_cast(&v6addr);
        int              connfd  = ::accept(sockfd_, addr, &addrlen);
        if (connfd < 0) {  // continue accept
            // int savedErrno = errno;
            SYSTEM_ERROR << ("Socket::accept");
            // switch (savedErrno) {
            //     case EAGAIN:
            //     case ECONNABORTED:
            //     case EINTR:
            //     case EPROTO:  // ???
            //     case EPERM:
            //     case EMFILE:  // per-process lmit of open file desctiptor ???
            //     case EBADF:
            //     case EFAULT:
            //     case EINVAL:
            //     case ENFILE:
            //     case ENOBUFS:
            //     case ENOMEM:
            //     case ENOTSOCK:
            //     case EOPNOTSUPP:
            //         // expected errors
            //         errno = savedErrno;
            //         break;
            //         // unexpected errors
            //         // throw system_exception("accept socket unexpected errors");
            //         break;
            //     default: {
            //         throw system_exception("accept socket unknown errors");
            //     } break;
            // }
        }
        if (connfd > 0) {
            SocketOpt::setNonBlockAndCloseOnExec(connfd);
            SocketOpt::setTcpKeepAlive(true, connfd);
            if (addr->sa_family == AF_INET) {
                const auto v4PAdr = std::dynamic_pointer_cast<IpV4Address>(peeraddr);
                v4PAdr->setSockAddr(*SocketOpt::sockaddr_in_cast(addr));
            }
            else if (addr->sa_family == AF_INET6) {
                const auto v6PAdr = std::dynamic_pointer_cast<IpV6Address>(peeraddr);
                v6PAdr->setSockAddr(v6addr);
            }
        }
        return connfd;
    }

    void TcpSocket::shutdownWrite() { SocketOpt::shutdownWrite(sockfd_); }

    void TcpSocket::setTcpNoDelay(bool on)
    {
        int optval = on ? 1 : 0;
        ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, static_cast<socklen_t>(sizeof optval));
    }

    void TcpSocket::setKeepAlive(bool on)
    {
        int optval = on ? 1 : 0;
        ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, static_cast<socklen_t>(sizeof optval));
    }
}  // namespace foundation
}  // namespace xncc